﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AmiEmuLib.AmigaMachine;
using AmiEmuLib.AmigaMachine.AmigaMemory;
using System.Diagnostics;
using System.Xml;
using TomanuExtensions;
using TomanuExtensions.Utils;

namespace AmiEmuLib.SaveState
{
    /// <summary>
    /// Part of memory bank. Continous memory range filled with random cells.
    /// In diff mode there can be space between ranges.
    /// </summary>
    [DebuggerDisplay("Area, {BankType}, Address: {Address}, Size = {Size}")]
    public class AreaMemoryRange : MemoryRange
    {
        /// <summary>
        /// Used when serializing to xml. Number of chars in <line><</line> element.
        /// </summary>
        private const int CHARS_IN_LINE = 64;

        /// <summary>
        /// Memory content. 
        /// </summary>
        private ushort[] m_words;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_reader"></param>
        /// <param name="a_bank_state"></param>
        internal AreaMemoryRange(XmlReader a_reader, MemoryBankState a_bank_state)
            : base(a_reader, a_bank_state)
        {
            a_reader.ReadStartElement("Area");

            m_words = new ushort[Size / 2];

            if (Size <= CHARS_IN_LINE / 4)
            {
                string hex = a_reader.ReadString();
                MemoryHelpers.HexToAmigaMemory(hex, m_words, 0);
            }
            else
            {
                uint word_index = 0;
                while (word_index < m_words.Length)
                {
                    a_reader.ReadStartElement("Line");

                    string hex = a_reader.ReadString();
                    MemoryHelpers.HexToAmigaMemory(hex, m_words, word_index);

                    a_reader.ReadEndElement();

                    word_index += CHARS_IN_LINE / 4;
                }
            }

            a_reader.ReadEndElement();
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_bank_state"></param>
        /// <param name="a_address"></param>
        /// <param name="a_size"></param>
        internal AreaMemoryRange(MemoryBankState a_bank_state, uint a_address, MemorySize a_size) 
            : base(a_bank_state, a_address, a_size)
        {
        }

        internal override void Restore(Memory a_memory)
        {
            for (uint addr = Address; addr < Address + Size; addr += 2)
                a_memory.SetWord(addr, m_words[(int)(addr - Address) / 2]);
        }

        internal override ushort GetWord(uint a_address)
        {
            if (!MemoryHelpers.IsWordAllign(a_address))
                throw new ArgumentException();

            return m_words[(a_address - Address) / 2];
        }

        internal override void Persist(MemoryBank a_bank)
        {
            m_words = new ushort[Size / 2];

            for (uint addr = Address; addr <= LastWordAddress; addr += 2)
                m_words[(addr - Address) / 2] = a_bank.GetWord(addr);
        }

        internal override void WriteXml(XmlWriter a_writer)
        {
            base.WriteXml(a_writer);

            if (Size <= CHARS_IN_LINE / 4)
            {
                string line = MemoryHelpers.AmigaMemoryToHex(m_words, 0, Size / 2);
                a_writer.WriteString(line);
            }
            else
            {
                uint word_index = 0;
                while (word_index < m_words.Length)
                {
                    a_writer.WriteStartElement("Line");

                    a_writer.WriteHexAttribute("Address", Address + (uint)word_index * 2);
                    uint s = CHARS_IN_LINE / 4;
                    if (word_index + s >= m_words.Length)
                        s = (uint)m_words.Length - word_index;
                    string line = MemoryHelpers.AmigaMemoryToHex(m_words, word_index, s);
                    a_writer.WriteString(line);

                    a_writer.WriteEndElement();

                    word_index += CHARS_IN_LINE / 4;
                }
            }
        }

        public override bool Equals(object a_obj)
        {
            if (a_obj == null)
                return false;
            AreaMemoryRange range = a_obj as AreaMemoryRange;
            if (range == null)
                return false;

            return (range.Address == Address) && (range.Size == Size) && 
                   range.m_words.AreSame(m_words);
        }

        public override int GetHashCode()
        {
            return Address.GetHashCode() ^ Size.GetHashCode() ^ ArrayExtensions.GetHashCode(m_words);
        }

        internal override uint GetCharSize()
        {
            return AreaMemoryRange.GetCharSize(Size);
        }

        /// <summary>
        /// Numbers of char (with end lines and indentation spaces) of xml content.
        /// </summary>
        /// <param name="a_size"></param>
        /// <returns></returns>
        internal static uint GetCharSize(uint a_size)
        {
            if (a_size == 0)
                return 0;

            //0000000000111111111222222222233333333334444444444555555555556666666666777777777
            //1234567890123456789012345678901234567890123456789012345678901234567890123456789
            //      <Area Address="0x00FC0000" Size="">
            //        <Line Address="0x00FC0000"></Line>
            //      </Area>
            //      <Area Address="0x00FC8D26" Size=""></Area>
            if (a_size < AreaMemoryRange.CHARS_IN_LINE / 2)
                return 49 + ((uint)Math.Log10(a_size) + 1) + (a_size * 2);
            else
            {
                return 42 + 14 + ((uint)Math.Log10(a_size) + 1) + (a_size * 2) +
                       (a_size / (AreaMemoryRange.CHARS_IN_LINE / 2)) * 43;
            }
        }
    }
}
